﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;

namespace Weka
{
    internal abstract class ObjectSerialiserProvider { }
    internal class ObjectSerialiserProvider<T> : ObjectSerialiserProvider
    {
        //        private readonly Dictionary<Type, ObjectSerialiserProvider> _cachedTypeSerlisers;
        public MessageIdAttribute MessageId { get; private set; }
        private readonly FieldSerialiserProvider<T>[] _fieldSerialiserProviders;
        public ObjectSerialiserProvider(Dictionary<Type, ObjectSerialiserProvider> cachedTypeSerlisers)
        {
            //            _cachedTypeSerlisers = cachedTypeSerlisers;
            var midas = typeof(T).GetCustomAttributes(typeof(MessageIdAttribute), false);
            if (midas.Length == 0) throw new Exception("Type '" + typeof(T).Name + "' does not have the MessageId attribute.");
            MessageId = ((MessageIdAttribute)midas[0]);

            var fop = typeof(T).GetAllProperties()
                        .Where(p => p.CanRead)
                        .Select(p => new { M = p.GetGetMethod(true), FA = (FieldAttribute)p.GetCustomAttributes(typeof(FieldAttribute), false).FirstOrDefault() })
                        .Where(f => f.FA != null);

            var fom = typeof(T).GetAllMethods()
                        .Where(m => m.ReturnType != typeof(void) && m.GetParameters().Length == 0)
                        .Select(m => new { M = m, FA = (FieldAttribute)m.GetCustomAttributes(typeof(FieldAttribute), false).FirstOrDefault() })
                        .Where(f => f.FA != null);

            _fieldSerialiserProviders = fop
                    .Union(fom)
                    .OrderBy(f => f.FA.Ordinal)
                    .ThenBy(f => f.FA.Id)
                    .Select(f => CreateFieldSerialiserProvider(f.M, f.FA, cachedTypeSerlisers))
                    .Where(q => q != null)
                    .ToArray();
        }

        private FieldSerialiserProvider<T> CreateFieldSerialiserProvider(MethodInfo methodInfo, FieldAttribute field, Dictionary<Type, ObjectSerialiserProvider> cachedTypeSerlisers)
        {
            var rt = methodInfo.ReturnType;

            FieldSerialiserProvider<T> e1 = null;

            if (rt.IsValueType)
            {
                e1 = CreateSerialiserProviderForStruct(methodInfo, rt);
            }
            else//Reference type
            {
                if (rt == typeof(string))
                {
                    e1 = new FieldSerialiserProviderString<T>() { Reader = methodInfo.CreateDelegate<T, string>() };
                }
                else if (rt.IsAMessage())
                {
                    return CreateSerialiserProviderForMessage(methodInfo, field, cachedTypeSerlisers, rt);
                }
                else if (rt.GetIEnumerableType().IsAMessage())
                {
                    return CreateSerialiserProviderForMessageList(methodInfo, field, cachedTypeSerlisers, rt.GetIEnumerableType());
                }
            }


            if (e1 == null) return null;

            e1.FieldInfo = new FieldInfo(field, methodInfo.ReturnType.GetDataType());

            return e1;
        }

        private static FieldSerialiserProvider<T> CreateSerialiserProviderForMessageList(MethodInfo methodInfo, FieldAttribute field, Dictionary<Type, ObjectSerialiserProvider> cachedTypeSerlisers, Type rt)
        {
            Type dMessageListType = typeof(FieldSerialiserProviderMessageList<,>).MakeGenericType(typeof(T), rt);
            var createMethod = dMessageListType.GetMethod("Create", BindingFlags.Static | BindingFlags.Public);
            FieldSerialiserProvider<T> e2 = (FieldSerialiserProvider<T>)createMethod.Invoke(null, new object[] { cachedTypeSerlisers, methodInfo });
            e2.FieldInfo = new FieldInfo(field, rt.GetMessageId(), true);
            return e2;
        }

        private static FieldSerialiserProvider<T> CreateSerialiserProviderForMessage(MethodInfo methodInfo, FieldAttribute field, Dictionary<Type, ObjectSerialiserProvider> cachedTypeSerlisers, Type rt)
        {
            Type dMessageType = typeof(FieldSerialiserProviderMessage<,>).MakeGenericType(typeof(T), rt);
            var createMethod = dMessageType.GetMethod("Create", BindingFlags.Static | BindingFlags.Public);
            FieldSerialiserProvider<T> e2 = (FieldSerialiserProvider<T>)createMethod.Invoke(null, new object[] { cachedTypeSerlisers, methodInfo });
            e2.FieldInfo = new FieldInfo(field, rt.GetMessageId(), false);
            return e2;
        }

        private static FieldSerialiserProvider<T> CreateSerialiserProviderForStruct(MethodInfo methodInfo, Type rt)
        {
            if (rt.IsNullable())
            {
                if (rt == typeof(Int32?))
                    return new FieldSerialiserProviderGen<T, FieldSerialiserInt32N<T>, Int32?>() { Reader = methodInfo.CreateDelegate<T, Int32?>() };
                //                   return new FieldSerialiserProviderInt32N<T>() { Reader = methodInfo.CreateDelegate<T, Int32?>() };
                else if (rt == typeof(DateTime?))
                    return new FieldSerialiserProviderDateTimeN<T>() { Reader = methodInfo.CreateDelegate<T, DateTime?>() };
                else if (rt == typeof(bool?))
                    return new FieldSerialiserProviderBooleanN<T>() { Reader = methodInfo.CreateDelegate<T, bool?>() };
                else if (rt == typeof(decimal?))
                    return new FieldSerialiserProviderGen<T, FieldSerialiserDecimal<T>, decimal?>() { Reader = methodInfo.CreateDelegate<T, decimal?>() };
                else if (rt.GetNullableType().IsEnum)
                    return new FieldSerialiserProviderGen<T, FieldSerialiserInt32N<T>, Int32?>() { Reader = methodInfo.CreateEnumNullableDelegate<T, Int32>() };
            }
            else
            {
                if (rt == typeof(Int32))
                    return new FieldSerialiserProviderInt32<T>() { Reader = methodInfo.CreateDelegate<T, Int32>() };
                else if (rt == typeof(DateTime))
                    return CreateSerialiserProviderForDateTime(methodInfo);
                else if (rt == typeof(bool))
                    return new FieldSerialiserProviderGen<T, FieldSerialiserBoolean<T>, bool>() { Reader = methodInfo.CreateDelegate<T, bool>() };
                else if (rt == typeof(decimal))
                    return new FieldSerialiserProviderGenNN<T, FieldSerialiserDecimal<T>, decimal>() { Reader = methodInfo.CreateDelegate<T, decimal>() };
                else if (rt.IsEnum)
                    return new FieldSerialiserProviderInt32<T>() { Reader = methodInfo.CreateEnumDelegate<T, Int32>() };
            }
            throw new NotImplementedException("Serialisation of type'" + rt.FullName + "' is not currently Implemented.");
        }

        private static FieldSerialiserProvider<T> CreateSerialiserProviderForDateTime(MethodInfo methodInfo)
        {
            var rdr = methodInfo.CreateDelegate<T, DateTime>();
            return new FieldSerialiserProviderDateTimeN<T>() { Reader = t => rdr(t) };
        }

        public ObjectSerialiser<T> Create()
        {
            FieldSerialiser<T>[] gs = new FieldSerialiser<T>[_fieldSerialiserProviders.Length];

            for (int i = 0; i < gs.Length; i++)
                gs[i] = _fieldSerialiserProviders[i].CreateFieldSerialiser();

            return new ObjectSerialiser<T>() { MessageId = MessageId, _fieldSerialisers = gs };
        }
    }
}
